package edu.ccut.saturn.component.impl;

import java.util.List;

import edu.ccut.saturn.component.ILogicComponentInfo;
import edu.ccut.saturn.component.ILogicContext;
import edu.ccut.saturn.component.INodeInfo;
import edu.ccut.saturn.component.IParameterContext;
import edu.ccut.saturn.component.IParameterInfo;
import edu.ccut.saturn.component.SaturnComponent;
import edu.ccut.saturn.component.SaturnComponentException;
import edu.ccut.saturn.component.impl.node.EndNodeInfo;
import edu.ccut.saturn.component.impl.node.StartNodeInfo;
import edu.ccut.saturn.component.utils.ExceptionUtils;
import edu.ccut.saturn.component.utils.MathUtil;

/**
 * Logic component use to implement component Type with logic.
 * 
 * <p>
 * Logic component like a process for handle a business logic. It can invoke
 * base component for handle real technology or invoke a sub-logic component.
 * 
 * @author LeslieGu
 * 
 */
public class SaturnLogicComponent extends SaturnComponent {

	private ILogicComponentInfo logicComponentInfo;

	public SaturnLogicComponent(ILogicComponentInfo logicComponentInfo) {
		super(logicComponentInfo.getFullKey());
		this.logicComponentInfo = logicComponentInfo;
	}

	@Override
	public void execute(IParameterContext parameterContext) throws Exception {		
		
		ILogicContext logicContext = null;
		boolean needCommit = false;
		boolean closeConnection = true;
		String isAutoRollBack = "false";
		StartNodeInfo startNodeInfo = (StartNodeInfo) this.logicComponentInfo
				.getStartNodeInfo();

		try {
			logicContext = this.createLogicContext(parameterContext);      			
            this.loggerStart(parameterContext, logicContext);
            
			if ("true".equals(startNodeInfo.getAutoRollBack())) {

				if (parameterContext.hasConnection()
						&& !parameterContext.getConnection().getAutoCommit()
						&& "supports".equals(parameterContext
								.getTransactionType())) {

					isAutoRollBack = "false";
					closeConnection = false;

				} else {
					isAutoRollBack = "true";
				}

			} else if (parameterContext.hasConnection()
					&& "supports".equals(parameterContext.getTransactionType())) {

				closeConnection = false;
			}

			if (isAutoRollBack.equals("true")) {
				logicContext.getDatabaseBroker().beginTransaction();
				needCommit = true;
			}
			doExcute(logicContext);
			this.resetParameterContext(parameterContext, logicContext);

		} catch (Exception e) {
			
			if ("true".equals(isAutoRollBack)) {
				logicContext.getDatabaseBroker().rollbackTracsaction();
				needCommit = false;
			}
			
			logger.error("LOGIC ERROR : {message : '" + ExceptionUtils.getExMessage(e) + "', process : '" + logicContext.getId() + "'}");
			
			throw e;
		} finally {

			if (needCommit) {
				logicContext.getDatabaseBroker().commitTransaction();
			}

			if (closeConnection) {
				// && !"supports".equals(parameterContext
				// .getTransactionType())
				if (logicContext != null) {
					logicContext.getDatabaseBroker().close();
				}
			}
		}
		
		this.loggerEnd(parameterContext, logicContext);
	}
	
	private void loggerStart(IParameterContext parameterContext, ILogicContext logicContext) {
		
		if(parameterContext.getLogicContext() == null) {
			
			logger.info("Logic-" + this.logicComponentInfo.getFullKey() + "-S: { descn : '" + this.logicComponentInfo.getDescription()  
					 + "', process : '" + this.getComponentFullKey() + "' }");		
		    } else {
		
		logger.info("Logic-" + this.logicComponentInfo.getFullKey() + "-S: { descn : '" + this.logicComponentInfo.getDescription() 
				 + "', key : '" + logicContext.getId()
		         + "', process : '" + parameterContext.getLogicContext().getId()+ "' }");
	    }
	}

	private void loggerEnd(IParameterContext parameterContext, ILogicContext logicContext) {
		if(parameterContext.getLogicContext() == null) {
			
			logger.debug("Logic-" + this.logicComponentInfo.getFullKey() + "-E: { descn : '" + this.logicComponentInfo.getDescription()  
					 + "', process : '" + this.getComponentFullKey() + "' }");		
		    } else {
		    	
		logger.debug("Logic-" + this.logicComponentInfo.getFullKey() + "-E: { descn : '" + this.logicComponentInfo.getDescription()
				 + "', key : '" + logicContext.getId()
				 + "', process : '" + parameterContext.getLogicContext().getId()+ "' }");
	       }
	}
	
	private ILogicContext createLogicContext(IParameterContext parameterContext)
			throws Exception {

		INodeInfo startNodeInfo = this.logicComponentInfo.getStartNodeInfo();
		List<IParameterInfo> parameterInfos = startNodeInfo.getParameterInfos();
		SaturnLogicContext logicContext = null;

		if (startNodeInfo instanceof StartNodeInfo) {

			if ("supports".equals(parameterContext.getTransactionType())
					&& parameterContext.hasConnection()) {

				logicContext = new SaturnLogicContext(this.logicComponentInfo
						.getFullKey(), parameterContext.getConnection(),
						parameterContext.getTransactionType());

			} else {
				logicContext = new SaturnLogicContext(this.logicComponentInfo
						.getFullKey() + " " + MathUtil.radom());
			}
		}
		int exceptedNum = parameterInfos.size();
		int actualNum = parameterContext.getParameters().size();

		if (exceptedNum != actualNum) {
			logger
					.warn("the number of the parameters we defined is not as the same as the actual parameters' number");
		}

		for (int i = 0; i < parameterInfos.size(); ++i) {
			IParameterInfo parameterInfo = parameterInfos.get(i);
			Object value = null;

			if (parameterContext.getParameter(i) != null) {
				value = parameterContext.getParameter(i).getValue();
			}
			logicContext.setValue(parameterInfo.getValueKey(), value);
		}
	//	logger.debug("LOGIC CONTEXT CREATED!");

		return logicContext;
	}

	// TODO:This is like the action component, can refact it?
	private void doExcute(ILogicContext logicContext) throws Exception {
		INodeInfo startNodeInfo = this.logicComponentInfo.getStartNodeInfo();
		String nextId = "start";
		INodeInfo nodeInfo = startNodeInfo;

		do {

			if (nodeInfo == null) {
				throw new SaturnComponentException("can't find node id = "
						+ nextId + "in logic component "
						+ this.logicComponentInfo.getFullKey());
			}
			nodeInfo.execute(logicContext);

			if (nodeInfo instanceof EndNodeInfo) {
				break;
			}
			nextId = logicContext.getNextNodeId(nodeInfo.getId());

			if (nextId == null) {
				nextId = nodeInfo.getNextId();

			} else {
				logicContext.removeExceptionRouter(nodeInfo.getId());
			}
			nodeInfo = this.logicComponentInfo.getNodeInfo(nextId);

		} while (nextId != null);
	}

	private void resetParameterContext(IParameterContext parameterContext,
			ILogicContext logicContext) {

		INodeInfo startNodeInfo = this.logicComponentInfo.getStartNodeInfo();
		List<IParameterInfo> parameterInfos = startNodeInfo.getParameterInfos();

		for (int i = 0; i < parameterInfos.size(); ++i) {
			IParameterInfo parameterInfo = parameterInfos.get(i);

			if (IParameterInfo.PASSING_OUT.equals(parameterInfo
					.getPassingType())
					|| IParameterInfo.PASSING_IN_OUT.equals(parameterInfo
							.getPassingType())) {

				parameterContext.setParameter(i, logicContext
						.getValue(parameterInfo.getValueKey()));
			}
		}
	}
}
